#!/usr/bin/env python3
#
# KVM Flight Recorder - ring buffer tracing script
#
# Copyright (C) 2012 IBM Corp
#
# Author: Stefan Hajnoczi <stefanha@linux.vnet.ibm.com>
#
# This script provides a command-line interface to kvm ftrace and is designed
# to be used as a flight recorder that is always running.  To start in-memory
# recording:
#
# sudo kvm_flightrecorder start 8192  # 8 MB per-cpu ring buffers
#
# The per-cpu ring buffer size can be given in KB as an optional argument to
# the 'start' subcommand.
#
# To stop the flight recorder:
#
# sudo kvm_flightrecorder stop
#
# To dump the contents of the flight recorder (this can be done when the
# recorder is stopped or while it is running):
#
# sudo kvm_flightrecorder dump >/path/to/dump.txt
#
# To observe the trace while it is running, use the 'tail' subcommand:
#
# sudo kvm_flightrecorder tail
#
# Note that the flight recorder may impact overall system performance by
# consuming CPU cycles.  No disk I/O is performed since the ring buffer holds a
# fixed-size in-memory trace.

import sys
import os

tracing_dir = '/sys/kernel/debug/tracing'

def trace_path(*args):
    return os.path.join(tracing_dir, *args)

def write_file(path, data):
    open(path, 'wb').write(data)

def enable_event(subsystem, event, enable):
    write_file(trace_path('events', subsystem, event, 'enable'), '1' if enable else '0')

def enable_subsystem(subsystem, enable):
    write_file(trace_path('events', subsystem, 'enable'), '1' if enable else '0')

def start_tracing():
    enable_subsystem('kvm', True)
    write_file(trace_path('tracing_on'), '1')

def stop_tracing():
    write_file(trace_path('tracing_on'), '0')
    enable_subsystem('kvm', False)
    write_file(trace_path('events', 'enable'), '0')
    write_file(trace_path('current_tracer'), 'nop')

def dump_trace():
    tracefile = open(trace_path('trace'), 'r')
    try:
        lines = True
        while lines:
            lines = tracefile.readlines(64 * 1024)
            sys.stdout.writelines(lines)
    except KeyboardInterrupt:
        pass

def tail_trace():
    try:
        for line in open(trace_path('trace_pipe'), 'r'):
            sys.stdout.write(line)
    except KeyboardInterrupt:
        pass

def usage():
    print('Usage: %s start [buffer_size_kb] | stop | dump | tail' % sys.argv[0])
    print('Control the KVM flight recorder tracing.')
    sys.exit(0)

def main():
    if len(sys.argv) < 2:
        usage()

    cmd = sys.argv[1]
    if cmd == '--version':
        print('kvm_flightrecorder version 1.0')
        sys.exit(0)

    if not os.path.isdir(tracing_dir):
        print('Unable to tracing debugfs directory, try:')
        print('mount -t debugfs none /sys/kernel/debug')
        sys.exit(1)
    if not os.access(tracing_dir, os.W_OK):
        print('Unable to write to tracing debugfs directory, please run as root')
        sys.exit(1)

    if cmd == 'start':
        stop_tracing() # clean up first

        if len(sys.argv) == 3:
            try:
                buffer_size_kb = int(sys.argv[2])
            except ValueError:
                print('Invalid per-cpu trace buffer size in KB')
                sys.exit(1)
            write_file(trace_path('buffer_size_kb'), str(buffer_size_kb))
            print('Per-CPU ring buffer size set to %d KB' % buffer_size_kb)

        start_tracing()
        print('KVM flight recorder enabled')
    elif cmd == 'stop':
        stop_tracing()
        print('KVM flight recorder disabled')
    elif cmd == 'dump':
        dump_trace()
    elif cmd == 'tail':
        tail_trace()
    else:
        usage()

if __name__ == '__main__':
    sys.exit(main())
